/**
一种在go语言上面，持久写入文件的机制
*/
package filelasting

import (
	"fmt"
	"io"
	"os"
)

// 保存文件 ，如果文件不存在自动创建，如果文件存在，就在现有文件操作
/**
filename 文件名
data 文件内容
fileMode 文件权限
*/
func Save(filename string, data []byte, fileMode os.FileMode) error {
	file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_EXCL, fileMode)
	if err != nil {
		if !os.IsExist(err) {
			return fmt.Errorf("打开文件出错： %s: %s", filename, err)
		}
		// 判断文件名称与字节数组内容 ，如果内容一样，修改文件权限后退出。
		if dataMatches(filename, data) {
			chmod(filename, fileMode)
			return nil
		}
		// 打开文件
		file, err = openAndTruncate(filename, fileMode, true)
		if err != nil {
			return fmt.Errorf("无法截断： %s: %s", filename, err)
		}
	}

	_, err = file.Write(data)
	if err != nil {
		if err := os.Remove(filename); err != nil {
			fmt.Errorf("无法删除文件: %v", err)
		}
		return fmt.Errorf("无法写入文件: %s: %s", filename, err)
	}
	if err := file.Sync(); err != nil {
		fmt.Errorf("无法同步文件: %v", err)
	}
	if err := file.Close(); err != nil {
		fmt.Errorf("无法关闭文件: %v", err)
	}

	fmt.Println("文件保存完成")
	return nil
}

// 打开文件，并且返回file操作类
func openAndTruncate(filename string, fileMode os.FileMode, removeIfNecessary bool) (*os.File, error) {
	file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
	if err != nil && os.IsPermission(err) && removeIfNecessary {
		fmt.Println("权限被拒绝截断文件： %v, 尝试删除。", filename)
		err = os.Remove(filename)
		if err != nil {
			return nil, fmt.Errorf("无法删除文件： %v: %v", filename, err)
		}
		return openAndTruncate(filename, fileMode, false)
	}

	return file, err
}

/**
比较文件名以及内容
filename 文件名
data 文件内容
*/
func dataMatches(filename string, data []byte) bool {
	file, err := os.OpenFile(filename, os.O_RDONLY, 0)
	if err != nil {
		fmt.Println("无法在 %s 打开现有文件进行读取: %s", filename, err)
		return false
	}
	fileInfo, err := file.Stat()
	if err != nil {
		fmt.Println("获取文件结构失败： %s", filename)
		return false
	}
	if fileInfo.Size() != int64(len(data)) {
		return false
	}
	b := make([]byte, 65536)
	i := 0
	for {
		n, err := file.Read(b)
		if err != nil && err != io.EOF {
			fmt.Println("读取文件 %s 数据失败: %s", filename, err)
			return false
		}
		for j := 0; j < n; j++ {
			if b[j] != data[i] {
				return false
			}
			i = i + 1
		}
		if err == io.EOF {
			break
		}
	}
	return true
}

func chmod(filename string, fileMode os.FileMode) {
	fi, err := os.Stat(filename)
	if err != nil || fi.Mode() != fileMode {
		fmt.Println("权限修改 %v", filename)
		err = os.Chmod(filename, fileMode)
		if err != nil {
			fmt.Println("无法修改权限： %v: %v", filename, err)
		}
	}
}
